/** * 服务端 API 代理 —— 将 /api/** 请求转发到 ThinkJS 后端 * * 对齐 ai_uniapp_v2 H5 端的 manifest.json devServer.proxy 行为: * /api/user/info → https://api.jiefuku.com/user/info * * 用途: * 1. 避免浏览器跨域 * 2. 服务端转发自动携带 cookie,保持登录态 * * 环境变量 NUXT_PUBLIC_API_BASE 可覆盖目标地址(生产环境指向 https://api.aionline.cc) */ import { proxyRequest, sendWebResponse } from 'h3' export default defineEventHandler(async (event) => { const config = useRuntimeConfig(event) const apiBase = String(config.public.apiBase || '').replace(/\/+$/, '') if (!apiBase) { throw createError({ statusCode: 500, statusMessage: 'NUXT_PUBLIC_API_BASE is not configured', }) } // 去掉 /api 前缀,得到真实后端路径 let path = event.path.replace(/^\/api/, '') || '/' if (!path.startsWith('/')) path = '/' + path const method = event.method const url = apiBase + path const headers = {} // 转发用户 cookie(保持登录态) const cookie = event.headers.get('cookie') if (cookie) { headers.cookie = cookie } // 透传客户端的 Content-Type(multipart/form-data 上传需要) const contentType = event.headers.get('content-type') if (contentType) { headers['content-type'] = contentType } const isMultipart = Boolean(contentType?.includes('multipart/form-data')) // 透传签名的 header(nonce / timestr / token) const clientNonce = event.headers.get('nonce') const clientTimestr = event.headers.get('timestr') const clientToken = event.headers.get('token') const clientAccept = event.headers.get('accept') if (clientNonce) headers.nonce = clientNonce if (clientTimestr) headers.timestr = clientTimestr if (clientToken) headers.token = clientToken if (clientAccept) headers.accept = clientAccept const fetchOptions = { method, headers } // GET: 查询参数 const query = getQuery(event) const queryStr = new URLSearchParams(query).toString() const fullUrl = queryStr ? url + '?' + queryStr : url // Multipart 必须直接转发原始请求流。先 readRawBody() 在部分 Chromium / // Nitro 组合下会一直等待流结束,导致上传请求既不到后端也不返回。 if (isMultipart) { return proxyRequest(event, fullUrl, { headers }) } // POST: 请求体 if (method === 'POST') { const body = await readBody(event) if (body && typeof body === 'object') { fetchOptions.body = new URLSearchParams(body).toString() } } try { // SSE 必须透传原始 Web Response;$fetch 会等待响应结束并尝试解析, // 导致浏览器无法逐块收到上游事件。 // Multipart 同样使用原生 fetch,确保二进制请求体和 boundary 原样到达上游。 if ((clientAccept || '').includes('text/event-stream')) { const upstreamResponse = await fetch(fullUrl, fetchOptions) return sendWebResponse(event, upstreamResponse) } return await $fetch(fullUrl, fetchOptions) } catch (error) { console.error('[Server Proxy Error]', url, error.message) return { code: 1000, data: null, msg: '服务器代理请求失败', } } })